/*
 *   Copyright 1999-2016 Asiainfo Technologies(China),Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.asiainfo.redis.service;

import com.asiainfo.redis.entity.ClusterNode;
import com.asiainfo.redis.entity.ClusterNodeConfig;
import com.asiainfo.redis.entity.Host;
import com.asiainfo.redis.repository.ClusterNodeRepository;
import com.asiainfo.redis.repository.HostRepository;
import com.asiainfo.redis.utils.ShellUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * @version v 1.0 on 2016/5/9 14:12
 * @auther william.xu
 */
@Service
public class HostService {

    @Value("${redis.cluster.path}")
    private String prefixPath;

    @Value("classpath:${redis.cluster.init.file}")
    private org.springframework.core.io.Resource redisClusterInitFile;

    @Value("classpath:${redis.build.file}")
    private org.springframework.core.io.Resource redisBuildFile;

    @Resource
    private ClusterNodeRepository clusterNodeRepository;

    @Resource
    private HostRepository hostRepository;

    public Host findHost(Long hostId) throws Exception {
        Host host = hostRepository.findOne(hostId);
        if (host == null) {
            throw new Exception("找不到hostId=" + hostId);
        }
        return host;
    }


    @Transactional(rollbackFor = Exception.class)
    public void saveHost(Host redisHost) throws Exception {
        boolean isNewHost = false;
        String errorMsg = "该主机IP(" + redisHost.getIp() + ")已经存在！";
        try {
            if (redisHost.getId() == null) {
                isNewHost = true;
                hostRepository.save(redisHost);
            } else {
                Host entity = hostRepository.findOne(redisHost.getId());
                BeanUtils.copyProperties(redisHost, entity);
                hostRepository.save(entity);
            }
            //在使用@Transactional事务处理情况下，hibernate会使用batch update，也就是在整个代码块结束后才批量发送执行的sql到数据库
            //如果在代码中没有碰到查询语句，由于都没有执行sql，所以不会有任何异常抛出，只有在调用外层捕获到。
            //但是如果在代码中碰到查询语句，将立即把之前的若干update语句送到数据库去执行
            //所以执行以下查询时，将先执行以上save语句
            //如果数据库对ip设置了唯一性约束，将抛出DataIntegrityViolationException
            //如果数据库没有对ip设置唯一性约束，则查询出来两条记录，防止重复ip，需要人为抛出异常
            List<Host> list = hostRepository.findByIp(redisHost.getIp());
            if (list.size() > 1){
                throw new Exception(errorMsg);
            }
        } catch (DataIntegrityViolationException e) {
            throw new Exception(errorMsg);
        }
        if (isNewHost) {
//            try{
//                String result = ssh(redisHost, "redis-server -v");
//                if (!(result != null && result.contains("Redis server v=3.0"))){
//                    throw new Exception("请安装redis 3.0以上服务");
//                }
//            }catch (Exception e){
//                throw new Exception("请检查该主机是否正常ssh登录，并安装redis 3.0以上服务：" +e.getMessage());
//            }
            try {
                uploadRedisBuildFile(redisHost, redisBuildFile.getFilename());
            } catch (Exception e) {
                throw new Exception("上传redis安装文件失败：" + e.getMessage());
            }
            try {
                String result = ssh(redisHost, "~/redis/src/redis-server -v");
                if (!(result != null && result.contains("Redis server v=3."))) {
                    throw new Exception("安装redis 3.x服务失败：" + result);
                }
            } catch (Exception e) {
                throw new Exception("安装redis服务失败：" + e.getMessage());
            }
            try {
                String destFile = prefixPath + "/" + redisClusterInitFile.getFilename();
                uploadRedisInitFile(redisHost, destFile);
            } catch (Exception e) {
                throw new Exception("上传redis集群初始化文件失败：" + e.getMessage());
            }
        }
    }

    public Page<Host> getHostList(String ipKey, Pageable pageable) {
        if (ipKey == null) {
            ipKey = "";
        }
        return hostRepository.findByIpkey(ipKey, pageable);
    }


    //启动redis节点
    public void startRedisNode(ClusterNode clusterNode) throws Exception {
        String script = "rm -rf dump.rdb;" + prefixPath + "/" + clusterNode.getPort() + "/start-redis";
        Host host = hostRepository.findOne(clusterNode.getHostId());
        ssh(host, script);
    }

    public void resetRedisClusterNode(ClusterNode clusterNode) throws Exception{
        String script = ("rm -rf dump.rdb;" + prefixPath + "/" + clusterNode.getPort() + "/stop-redis;sleep 5;");
        script += ("rm -rf " + prefixPath + "/" + clusterNode.getPort() + "/appendonly.aof;");
        script += ("rm -rf " + prefixPath + "/" + clusterNode.getPort() + "/dump.rdb;");
        script += (prefixPath + "/" + clusterNode.getPort() + "/start-redis");
        Host host = hostRepository.findOne(clusterNode.getHostId());
        ssh(host, script);
    }

    public void deleteNodesConfigFile(ClusterNode clusterNode) throws Exception {
        String script = "rm -rf " + prefixPath + "/" + clusterNode.getPort() + "/nodes.conf";
        Host host = hostRepository.findOne(clusterNode.getHostId());
        ssh(host, script);
    }

    public void uploadRedisBuildFile(Host host, String destFile) throws Exception {
        ShellUtil.sftp(host, redisBuildFile.getInputStream(), destFile);
        ssh(host, "tar xf " + destFile);
    }

    public void uploadRedisInitFile(Host host, String destFile) throws Exception {
        ssh(host, "mkdir -p " + prefixPath);
        ShellUtil.sftp(host, redisClusterInitFile.getInputStream(), destFile);
        ssh(host, "chmod 755 " + destFile);
    }

    public String ssh(Host host, String script) throws Exception {
        return ShellUtil.ssh(host, script);
    }

    public void initRedisNode(ClusterNodeConfig clusterNodeConfig, ClusterNode clusterNode) throws Exception {
        Host host = hostRepository.findOne(clusterNode.getHostId());
        //ssh(host, prefixPath + "/" + redisClusterInitFile.getFilename() + " " + clusterNode.getPort() + " " +
        // clusterNode.getPort());
        String path = "/home/" + host.getLoginUser() + "/" + prefixPath + "/" + clusterNode.getPort();
        String script = "python " + prefixPath + "/" + redisClusterInitFile.getFilename() + " -c -p " + path + " -P "
                + clusterNode.getPort();
        if (clusterNodeConfig.getMaxMemory() != null) {
            script += (" -m " + clusterNodeConfig.getMaxMemory() + "m");
        }
        String appendOnly = clusterNodeConfig.getAppendOnly();
        if (appendOnly != null && !("".equals(appendOnly))) {
            if ("yes".equals(appendOnly)) {
                script += " -a ";
            } else {
                script += (" -s " + clusterNodeConfig.getAppendOnlyNoSeconds() + ":" + clusterNodeConfig
                        .getAppendOnlyNoKeys());
            }
        }
        ssh(host, script);
    }

    public void operClusterNode(Long nodeId, boolean isStart) throws Exception {
        ClusterNode node = clusterNodeRepository.findOne(nodeId);
        if (node == null) {
            throw new Exception("找不到该ClusterNode，id=" + nodeId);
        }
        Host host = findHost(node.getHostId());
        String script = prefixPath + "/" + node.getPort();
        String oper = "";
        if (isStart) {
            script += "/start-redis";
            oper = "启动";
        } else {
            script += "/stop-redis";
            oper = "停止";
        }
        try {
            ssh(host, script);
        } catch (Exception e) {
            throw new Exception(oper + "节点发生错误：" + e.getMessage());
        }
    }


}
